package org.lex.ftp.ui.explorer.directory;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;

import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.lex.catalog.CatalogEvent;
import org.lex.catalog.CatalogListener;
import org.lex.ftp.model.FTPModel;
import org.lex.ftp.model.NullFTPModel;
import org.lex.net.ftp.IFTPFile;
import org.lex.utils.BinarySearch;
import org.lex.utils.text.Splitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FTPDirectoryExplorer extends JTree implements CatalogListener {
	private DefaultTreeModel treeModel;
	private FTPModel ftpModel = NullFTPModel.getInstance();
	private Logger log = LoggerFactory.getLogger(FTPDirectoryExplorer.class);

	public FTPDirectoryExplorer() {
		super();
		initialize();
	}

	public void setFTPModel(FTPModel model) {
		this.ftpModel.removeCatalogListener(this);
		this.ftpModel = model;
		this.ftpModel.addCatalogListener(this);
		((FTPFileNode) this.treeModel.getRoot()).removeAllChildren();
		this.treeModel.nodeStructureChanged((TreeNode) treeModel.getRoot());
		this.catalogChanged(new CatalogEvent(ftpModel, model.getCatalog()));
	}

	@Override
	public DefaultTreeModel getModel() {
		if (null == treeModel)
			treeModel = new DefaultTreeModel(new FTPFileNode("/"), false);
		return treeModel;
	}

	public void catalogChanged(CatalogEvent e) {
		final String directory = ftpModel.getCatalog();
		log.debug("sync directory with model: {}", directory);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				TreePath path = findFile(directory);
				if (null == path)
					return;
				FTPFileNode node = (FTPFileNode) path.getLastPathComponent();
				if (null == node)
					return;
				IFTPFile[] list = ftpModel.list();
				node.setContents(list);
				getModel().nodeChanged(node);
				setSelectionPath(path);
				expandPath(path);
				scrollPathToVisible(path);
			}
		});
	}

	/**
	 * Ensures the file is visible
	 * 
	 * @param fileToLocate
	 * @param selectFile
	 */
	private TreePath findFile(String directory) {
		if (directory == null || 0 == directory.length())
			return null;
		log.debug("try to find: {}", directory);
		// split the full path into individual to locate them in the tree
		String[] files = Splitter.BackslashSplitter.split(directory);
		log.debug("break path into: {}", Arrays.toString(files));
		FTPFileNode root = (FTPFileNode) this.getModel().getRoot();
		ArrayList<Object> path = new ArrayList<Object>();
		path.add(root);
		FTPFileNode current = root;
		int i = 1;
		while (i < files.length) {
			String dir = files[i];
			FTPFileNode node = this.hasChild(current, dir);
			if (null == node) {
				node = new FTPFileNode(dir);
				int pos = BinarySearch.search(current, dir);
				if (pos < 0)
					pos = -1 - pos;
				current.insert(node, pos);
			}
			current = node;
			path.add(current);
			i++;
		}
		log.debug("found: {}", path.toString());
		return new TreePath(path.toArray());
	}

	private FTPFileNode hasChild(FTPFileNode node, String name) {
		int childCount = node.getChildCount();
		for (int i = 0; i < childCount; i++) {
			FTPFileNode current = (FTPFileNode) node.getChildAt(i);
			if (name.equals(current.getFileName()))
				return current;
		}
		return null;
	}

	private void initialize() {
		this.setRootVisible(false);
		this.setModel(this.getModel());
		this.setToggleClickCount(1);
		this.setCellRenderer(new FtpFileTreeRenderer());
		this.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		// add selection listener
		this.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON1)
					fireSelectionChanged();
			}
		});
	}

	private void fireSelectionChanged() {
		TreePath path = this.getSelectionPath();
		if (null == path)
			return;
		StringBuilder buf = new StringBuilder("/");
		int count = path.getPathCount();
		for (int i = 1; i < count; i++) {
			buf.append(path.getPathComponent(i));
			if (i < count - 1)
				buf.append('/');
		}
		ftpModel.setCatalog(buf.toString());
	}
}
